/**
 * 
 */
package jmine.tec.report.impl;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import jmine.tec.report.CurriedDataColumn;
import jmine.tec.report.DataColumn;
import jmine.tec.report.ExecutableDataColumn;
import jmine.tec.report.ExecutableDataColumnContainer;
import jmine.tec.report.ExecutableDataTable;
import jmine.tec.report.metadata.ColumnMetaData;
import jmine.tec.report.metadata.ParameterDefinition;
import jmine.tec.report.metadata.ParameterType;
import jmine.tec.report.metadata.impl.SimpleColumnMetaData;

/**
 * @author takeshi
 * @version 17/09/2007
 */
public class AbstractTableDescriptor {

    private final List<DataColumn> columns = new ArrayList<DataColumn>();

    private Map<String, ParameterDefinition> mergedParametersDefinitions = new HashMap<String, ParameterDefinition>();

    private Map<String, Object> parameterValues = new HashMap<String, Object>();

    /**
     * C'tor
     */
    public AbstractTableDescriptor() {
        super();
    }

    /**
     * @param column {@link ColumnMetaData}
     * @return itself
     */
    public DataColumn addColumn(final ColumnMetaData column) {
        final SimpleDataColumn col = new SimpleDataColumn(column);
        this.columns.add(col);
        this.mergeParams(column);
        return col;
    }

    /**
     * Merges the parameters from the given column
     * 
     * @param column the {@link ColumnMetaData}
     */
    private void mergeParams(final ColumnMetaData column) {
        this.mergedParametersDefinitions.putAll(column.getAllParameters());
    }

    /**
     * @param key the column key
     * @param name the column name
     * @param params the parameter definitions
     * @return this
     */
    public DataColumn addColumn(final String key, final String name, final ParameterDefinition... params) {
        ColumnMetaData meta = new SimpleColumnMetaData(key, name, params);
        final SimpleDataColumn col = new SimpleDataColumn(meta);
        this.columns.add(col);
        this.mergeParams(meta);
        return col;
    }

    /**
     * Adds a column, currying the given parameters
     * 
     * @param key the key
     * @param name the name
     * @param paramsToCurry the params to curry
     * @param params the other parameter definitions
     * @return {@link DataColumn}
     */
    public DataColumn addColumnCurry(final String key, final String name, final Map<String, Object> paramsToCurry,
            final ParameterDefinition... params) {
        ColumnMetaData meta = new SimpleColumnMetaData(key, name, params);
        return this.addColumnCurry(meta, paramsToCurry);
    }

    /**
     * @param container the {@link ExecutableDataColumnContainer}
     * @return {@link ExecutableDataTable}
     */
    public final ExecutableDataTable buildExecutableDataTable(final ExecutableDataColumnContainer container) {
        SimpleExecutableDataTable table = new SimpleExecutableDataTable();
        for (final DataColumn col : this.columns) {
            ExecutableDataColumn exeCol = container.findExecutableDataColumn(col.getKey());
            if (exeCol != null) {
                if (col instanceof CurriedDataColumn) {
                    CurriedDataColumn curry = (CurriedDataColumn) col;
                    final Map<String, Object> allCurriedParameters = curry.getAllCurriedParameters();
                    exeCol = new CurriedExecutableDataColumn(exeCol, allCurriedParameters);
                }
                table.addColumn(exeCol);
            } else {
                throw new IllegalArgumentException("Column not found: " + col.getKey());
            }
        }
        return table;
    }

    /**
     * @param index int
     * @return {@link DataColumn}
     * @see br.com.maps.fof.report.descriptor.TableDescriptor#getColumn(int)
     */
    public DataColumn getColumn(final int index) {
        return this.columns.get(index);
    }

    /**
     * @return {@link List} of {@link ColumnMetaData}
     */
    public List<DataColumn> getColumns() {
        return this.columns;
    }

    /**
     * @return {@link Map}
     * @see br.com.maps.fof.report.descriptor.TableDescriptor#getMergedParameterDefinition()
     */
    public Map<String, ParameterDefinition> getMergedParameterDefinition() {
        return new HashMap<String, ParameterDefinition>(this.mergedParametersDefinitions);
    }

    /**
     * @return int
     * @see br.com.maps.fof.report.descriptor.TableDescriptor#getNumberOfColumn()
     */
    public int getNumberOfColumn() {
        return this.columns.size();
    }

    /**
     * @param param a string
     * @return o object
     */
    public Object getParam(final String param) {
        Object val = this.parameterValues.get(param);
        if (val != null) {
            return val;
        }
        final ParameterDefinition par = this.mergedParametersDefinitions.get(param);
        return par == null ? null : par.getReference();
    }

    /**
     * @param paramName the parameter name
     * @return the parameter type or null
     * @see br.com.maps.fof.report.descriptor.TableDescriptor#getParameterType(java.lang.String)
     */
    public ParameterType getParameterType(final String paramName) {
        final ParameterDefinition par = this.mergedParametersDefinitions.get(paramName);
        return par == null ? null : par.getType();
    }

    /**
     * @return {@link Map}
     * @see br.com.maps.fof.report.descriptor.TableDescriptor#getParameterValues()
     */
    public Map<String, Object> getParameterValues() {
        return this.parameterValues;
    }

    /**
     * Adds a column
     * 
     * @param meta the {@link ColumnMetaData}
     * @param paramsToCurry the params to curry
     * @return {@link DataColumn}
     * @see br.com.maps.util.report.TableDescriptor#addColumnCurry(br.com.maps.util.report.metadata.ColumnMetaData, java.util.Map)
     */
    public DataColumn addColumnCurry(final ColumnMetaData meta, final Map<String, Object> paramsToCurry) {
        if (paramsToCurry.isEmpty()) {
            return this.addColumn(meta);
        }
        SimpleCurriedDataColumn col =
                new SimpleCurriedDataColumn(new SimpleDataColumn(meta.getKey(), meta.getLabel(), meta), paramsToCurry);
        this.columns.add(col);
        this.mergedParametersDefinitions.putAll(col.getMetaData().getAllParameterDefinitions());
        return col;
    }

    /**
     * @param index the index
     * @see br.com.maps.util.report.TableDescriptor#removeColumnWithIndex(int)
     */
    public void removeColumnWithIndex(final int index) {
        this.getColumns().remove(index);
        this.revalidateParameters();
    }

    /**
     * @see br.com.maps.util.report.TableDescriptor#revalidateParameters()
     */
    public void revalidateParameters() {
        this.mergedParametersDefinitions.clear();
        for (DataColumn col : this.columns) {
            this.mergedParametersDefinitions.putAll(col.getMetaData().getAllParameterDefinitions());
        }
    }

    /**
     * @param param o param�metro
     * @param obj o objeto
     */
    public void setParameterValue(final String param, final Object obj) {
        final ParameterDefinition par = this.mergedParametersDefinitions.get(param);
        if (par != null) {
            if (par.isCompatible(obj)) {
                this.parameterValues.put(param, obj);
            }
        }
    }

}
